/*
 * FileName: select.cc
 * Author: 李波(Edwin Lee)
 * Date: 2024-04-22 23:06:18
 * Version: 1.0
 * Description: 学习select的io复用技术
 */

#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
#include <list>
#include <errno.h>
#include <queue>
#include <algorithm>

//初始化服务端的监听端口
int initserver(int port);
int Accept(int lfd, struct sockaddr *sa, socklen_t *salenptr);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);

int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("usage:./select 6666\n");
		return -1;
	}
	
	//初始化服务端用于监听的socket
	int lfd = initserver(atoi(argv[1]));
	printf("listensock=%d\n",lfd);
	if(lfd<0)
	{
		printf("initserver() failed\n");
		return -1;
	}
	std::list<int> fds;
	std::queue<int> deletefds;
	fds.push_back(lfd);
	fd_set oldfds;
	FD_ZERO(&oldfds);
	FD_SET(lfd,&oldfds);
	int ret=0;
	char buf[1500]{0};
	int maxfd = lfd;
	for(;;)
	{
		fd_set readfds = oldfds;
		printf("before select");
		if(0 > (ret=select(maxfd+1,&readfds,NULL,NULL,NULL)))
		{
			perror("select failed");
			return -1;
		}
		else if (0==ret) 
		{
			continue;
		}
		for(auto it=fds.begin();it!=fds.end();++it)
		{
			if(FD_ISSET(*it,&readfds))
			{
				printf("*it=%d\n",*it);
				if(lfd == *it)
				{
					struct sockaddr_in cliaddr;
					socklen_t len = sizeof cliaddr;
					int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);
					if(cfd<0)
					{
						perror("accept() failed");
						continue;
					}
					printf("new client ip=%s port=%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
					fds.push_back(cfd);
					FD_SET(cfd,&oldfds);
					if(maxfd<cfd)
						maxfd=cfd;
				}
				else 
				{
					memset(buf,0,sizeof buf);
					int n = Read(*it, buf, sizeof(buf)-1);
					if(n<=0)
					{
						perror("read failed");
						deletefds.push(*it);
						FD_CLR(*it,&oldfds);
						close(*it);
						printf("close cfd=%d\n",*it);
						if((*it)==maxfd)
						{
							for(int i=maxfd-1; i>2; ++i)
							{
								if(FD_ISSET(i,&oldfds))
								{
									maxfd = i;
									break;
								}
							}
						}
						continue;
					}
					printf("recv:%s\n",buf);
					//注意这里第三个参数填strlen(),而不是sizeof()
					Write(*it, buf, strlen(buf));	
				}
			}
		}
		while(!deletefds.empty())
		{
			fds.remove(deletefds.front());
			deletefds.pop();
		}

	}
	return 0;
}


int initserver(int port)
{
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket() failed");
		return -1;
	}

	//设置lfd的地址复用(未理解)
	int opt = 1;
	setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof opt);

	struct sockaddr_in servaddr;
	servaddr.sin_family = AF_INET;
	//设置任何人都能访问
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(port);
	
	if(bind(lfd,(struct sockaddr*)&servaddr,sizeof servaddr)<0)
	{
		perror("bind() failed");
		close(lfd);
		return -1;
	}

	if(listen(lfd,128)!=0)
	{
		perror("listen() failed");
		close(lfd);
		return -1;
	}
	
	return lfd;
}

int Accept(int lfd, struct sockaddr *sa, socklen_t *salenptr)
{
	int cfd;

	for(;;)
	{
		if ((cfd = accept(lfd, sa, salenptr)) < 0) 
		{
			//如果是被信号中断和软件层次中断,不能退出
			if ((errno == ECONNABORTED) || (errno == EINTR))
				continue;
			else
			{
				perror("accept error");
				exit(-1);
			}
		}
		break;
	}
	return cfd;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

	for(;;)
	{
		if ( (n = read(fd, ptr, nbytes)) == -1) 
		{
			if (errno == EINTR)//如果是被信号中断,不应该退出
				continue;
			else
				return -1;
		}
		break;
	}
	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

	for(;;)
	{
		if ( (n = write(fd, ptr, nbytes)) == -1) {
			if (errno == EINTR)
				continue;
			else
				return -1;
		}
		break;
	}
	return n;
}
